﻿using System;
using System.IO;
using System.Xml;
using System.Text;
using System.Collections.Generic;
using System.Reflection;

using Weazel.Gezel.Exporter;
using Weazel.Gezel.Model;
using Weazel.Gezel.Model.Errors;
using Weazel.Gezel.Parser;

using GezelModel = Weazel.Gezel.Model.Model;

namespace Weazel.Gezel.Exporter.ConsoleApp
{
  /// <summary>
  /// Enumerates the supported export formats
  /// </summary>
  enum ExportFormat
  {
    Xml,
    Gezel,
  }

  class Program
  {
    /// <summary>
    /// Name of the output file
    /// </summary>
    private static string outfile;

    /// <summary>
    /// Input gezel description
    /// </summary>
    private static string description;

    /// <summary>
    /// Current export format
    /// </summary>
    private static ExportFormat exportTo = ExportFormat.Gezel;

    static void Main(string[] args)
    {
      processArguments(args);

      GezelModel model = buildGezelModel();

      string output;
      switch (exportTo)
      {
        case ExportFormat.Xml:
          output = ModelExporter.ToXmlString(model);
          break;
        case ExportFormat.Gezel:
          output = ModelExporter.ToGezelString(model);
          break;
        default:
          throw new ApplicationException(string.Format("Unknown export format {0}", exportTo));
      }

      writeOutput(output);
    }

    /// <summary>
    /// Writes the output string to disk
    /// </summary>
    /// <param name="output"></param>
    private static void writeOutput(string output)
    {
      using (StreamWriter writer = new StreamWriter(outfile, false))
      {
        writer.Write(output);
      }
    }

    /// <summary>
    /// Constructs the gezel model from the 
    /// input file, possibly terminating the
    /// program if the file could not be parsed
    /// or if the model could not be verified.
    /// </summary>
    /// <returns>Gezel object model</returns>
    private static GezelModel buildGezelModel()
    {
      List<ParseError> parseErrors = new List<ParseError>();
      GezelModel model = ParseModel.Parse(ref parseErrors, description);

      if (parseErrors.Count > 0)
      {
        Console.Error.WriteLine("Conversion aborted because of the following parse errors:");
        foreach (ParseError error in parseErrors)
        {
          Console.Error.WriteLine(error.ErrorMessage.ToString());
        }

        System.Environment.Exit(1);
      }

      List<VerificationError> verificationErrors = new List<VerificationError>();
      List<VerificationWarning> verificationWarnings = new List<VerificationWarning>();
      model.Verify(ref verificationErrors, ref verificationWarnings);

      if (verificationErrors.Count > 0)
      {
        Console.Error.WriteLine("Conversion aborted because of the following verification errors:");
        foreach (VerificationError error in verificationErrors)
        {
          Console.Error.WriteLine(error.ErrorMessage.ToString());
        }

        System.Environment.Exit(1);
      }

      if (verificationWarnings.Count > 0)
      {
        Console.Error.WriteLine("There were {0} verification warnings:");
        foreach (VerificationWarning warning in verificationWarnings)
        {
          Console.Error.WriteLine(warning.ErrorMessage.ToString());
        }
      }
      return model;
    }

    /// <summary>
    /// Handles command line arguments
    /// </summary>
    /// <param name="args"></param>
    private static void processArguments(string[] arguments)
    {
      if (arguments.Length == 0)
      {
        Console.Error.WriteLine("Error: to few arguments");
        usage();
      }
      else if (arguments.Length > 1)
      {
        Console.WriteLine("Error: too many arguments");
        usage();
      }
      else
      {
        String filename = arguments[0];

        if (!File.Exists(filename))
        {
          Console.WriteLine("Error: file {0} does not exist", filename);
          usage();
        }
        else
        {
          // just take the original name and append '.xml' or '.fdl'
          switch (exportTo)
          {
            case ExportFormat.Xml:
              outfile = new FileInfo(filename).Name + ".xml";
              break;
            case ExportFormat.Gezel:
              outfile = new FileInfo(filename).Name + ".fdl";
              break; 
            default:
              throw new ApplicationException(string.Format("Unknown export format {0}", exportTo));
          }
        }

        try
        {
          using (StreamReader reader = new StreamReader(filename))
          {
            description = reader.ReadToEnd();
          }
        }
        catch (Exception)
        {
          Console.WriteLine("Error: cannot open file {0}", filename);
          usage();
        }
      }
    }

    /// <summary>
    /// Show a brief usage message and exit with exitcode 1 (error).
    /// </summary>
    private static void usage()
    {
      // get the name of the executable
      Assembly asm = Assembly.GetEntryAssembly();
      string executableName = asm.GetName().Name;

      Console.Error.WriteLine("\nUsage:\n\n{0}  <gezel model file>\n", executableName);
      Environment.Exit(1);
    }
  }
}
